package com.aosiding.miaosha.service;

import com.aosiding.miaosha.dao.OrderDAO;
import com.aosiding.miaosha.dao.StockDAO;
import com.aosiding.miaosha.dao.UserDAO;
import com.aosiding.miaosha.entity.Order;
import com.aosiding.miaosha.entity.Stock;
import com.aosiding.miaosha.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.DigestUtils;

import java.util.Date;
import java.util.concurrent.TimeUnit;

@Service
@Transactional
@Slf4j
public class OrderServiceImpl implements OrderService{

    @Autowired
    private StockDAO stockDAO;

    @Autowired
    private OrderDAO orderDAO;

    @Autowired
    private UserDAO userDAO;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //用来生成md5签名的方法
    @Override
    public String getMd5(Integer id, Integer userid) {
        //验证userid 存在用户信息
        //验证id     存在商品信息
        //生成md5签名放入redis服务

        //检验用户的合法性
        User user = userDAO.findById(userid);
        if(user==null)throw new RuntimeException("用户信息不存在!");
        log.info("用户信息:[{}]",user.toString());
        //检验商品的合法行
        Stock stock = stockDAO.checkStock(id);
        if(stock==null) throw new RuntimeException("商品信息不合法!");
        log.info("商品信息:[{}]",stock.toString());
        //生成hashkey
        String hashKey = "KEY_"+userid+"_"+id;
        //生成md5//这里!QS#是一个盐 随机生成
        String key = DigestUtils.md5DigestAsHex((userid+id+"!Q*jS#").getBytes());
        stringRedisTemplate.opsForValue().set(hashKey, key, 120, TimeUnit.SECONDS);
        log.info("Redis写入：[{}] [{}]", hashKey, key);
        return key;
    }

    @Override
    public int kill(Integer id, Integer userid, String md5) {
        //校验Redis中的秒杀商品是否超时
//        if (!stringRedisTemplate.hasKey("kill"+id)){
//            throw new RuntimeException("当前商品的抢购活动已经结束啦~~");
//        }

        //验证签名
        String hashKey = "KEY_"+userid+"_"+id;
        String s = stringRedisTemplate.opsForValue().get(hashKey);
        System.out.println(hashKey);
        System.out.println(s);
        if (s==null)  throw new RuntimeException("没有携带验证签名，请求不合法");
        if (!s.equals(md5)) {
            throw new RuntimeException("当前请求数据不合法，请稍后再试!");
        }

        //根据商品id，校验库存
        Stock stock = checkStock(id);
        //扣除库存
        updateSale(stock);
        //创建订单
        return createOrder(stock);
    }


    /**
     * 在非并发下没有问题
     * 在并发下必定发生超卖现象
     */
//    @Override
//    public int kill(Integer id) {
//        //根据商品id，校验库存
//        Stock stock = stockDAO.checkStock(id);
//        if (stock.getSale().equals(stock.getCount())){
//            throw new RuntimeException("库存不足");
//        }else {
//            //扣除库存
//            stock.setSale(stock.getSale()+1);
//            stockDAO.updateSale(stock);
//            //创建订单
//            Order order = new Order();
//            order.setSid(stock.getId()).setName(stock.getName()).setCreateDate(new Date());
//            orderDAO.createOrder(order);
//            return order.getId();
//        }
//
//    }

    /**
     *  * 悲观锁方式不推荐
     *该方法没有问题，可以解决并发问题，不会超卖，但是很慢
     * 有时会有问题，需要把最上面的 @Transactional 注释掉，不然会存在 @Transactional synchronized 的问题
     */
//    @Override
//    public synchronized int kill(Integer id) {
//        //根据商品id，校验库存
//        Stock stock = stockDAO.checkStock(id);
//        if (stock.getSale().equals(stock.getCount())){
//            throw new RuntimeException("库存不足");
//        }else {
//            //扣除库存
//            stock.setSale(stock.getSale()+1);
//            stockDAO.updateSale(stock);
//            //创建订单
//            Order order = new Order();
//            order.setSid(stock.getId()).setName(stock.getName()).setCreateDate(new Date());
//            orderDAO.createOrder(order);
//            return order.getId();
//        }
//
//    }

    /**
     *想使用同步代码块，也就是悲观锁解决，synchronized应放在调用处即StockController
     */
//    @Override
//    public int kill(Integer id) {
//        //根据商品id，校验库存
//        Stock stock = stockDAO.checkStock(id);
//        if (stock.getSale().equals(stock.getCount())){
//            throw new RuntimeException("库存不足");
//        }else {
//            //扣除库存
//            stock.setSale(stock.getSale()+1);
//            stockDAO.updateSale(stock);
//            //创建订单
//            Order order = new Order();
//            order.setSid(stock.getId()).setName(stock.getName()).setCreateDate(new Date());
//            orderDAO.createOrder(order);
//            return order.getId();
//        }
//    }

//    @Override
//    public int kill(Integer id) {
//        //校验Redis中的秒杀商品是否超时
//        if (!stringRedisTemplate.hasKey("kill"+id)){
//            throw new RuntimeException("当前商品的抢购活动已经结束啦~~");
//        }
//        //根据商品id，校验库存
//        Stock stock = checkStock(id);
//        //扣除库存
//        updateSale(stock);
//        //创建订单
//        return createOrder(stock);
//
//    }



    //校验库存
    private Stock checkStock(Integer id){
        Stock stock = stockDAO.checkStock(id);
        if (stock.getSale().equals(stock.getCount())){
            throw new RuntimeException("库存不足");
        }
        return stock;
    }

    /**
     * 乐观锁利用数据库事务，过滤其他请求
     * 使用乐观锁解决商品的超卖问题，实际上是把主要防止超卖问题交给数据库解决，
     * 利用数据库中定义的version以及数据库中的事务实现在并发情况下商品的超卖问题
     */
    //扣除库存1.0悲观锁
//    private void updateSale(Stock stock){
//        stock.setSale(stock.getSale()+1);
//        stockDAO.updateSale(stock);
//    }

    //扣除库存2.0乐观锁
    private void updateSale(Stock stock){
        //在sql层面完成销量的 +1, 和版本号 +1 ,并且根据商品id和版本号同时查询更新的商品
        int updateRows = stockDAO.updateSale2(stock);
        if(updateRows == 0){
            throw new RuntimeException("抢购失败,请重试");
        }
    }
    //创建订单
    private Integer createOrder(Stock stock){
        Order order = new Order();
        order.setSid(stock.getId()).setName(stock.getName()).setCreateDate(new Date());
        orderDAO.createOrder(order);
        return order.getId();
    }

}
